#region
//
// Bdev.Net.Dns by Rob Philpott, Big Developments Ltd. Please send all bugs/enhancements to
// rob@bigdevelopments.co.uk  This file and the code contained within is freeware and may be
// distributed and edited without restriction.
// 

#endregion

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;

namespace Bdev.Net.Dns
{
    /// <summary>
    /// A Request logically consists of a number of questions to ask the DNS Server. Create a request and
    /// add questions to it, then pass the request to Resolver.Lookup to query the DNS Server. It is important
    /// to note that many DNS Servers DO NOT SUPPORT MORE THAN 1 QUESTION PER REQUEST, and it is advised that
    /// you only add one question to a request. If not ensure you check Response.ReturnCode to see what the
    /// server has to say about it.
    /// </summary>
    public class Request
    {
        // A request is a series of questions, an 'opcode' (RFC1035 4.1.1) and a flag to denote
        // whether recursion is required (don't ask..., just assume it is)
        private ArrayList _questions;
        private bool _recursionDesired;
        private Opcode _opCode;

        public bool RecursionDesired
        {
            get { return _recursionDesired; }
            set { _recursionDesired = value; }
        }

        public Opcode Opcode
        {
            get { return _opCode; }
            set { _opCode = value; }
        }

        /// <summary>
        /// Construct this object with the default values and create an ArrayList to hold
        /// the questions as they are added
        /// </summary>
        public Request()
        {
            // default for a request is that recursion is desired and using standard query
            _recursionDesired = true;
            _opCode = Opcode.StandardQuery;

            // create an expandable list of questions
            _questions = new ArrayList();

        }

        /// <summary>
        /// Adds a question to the request to be sent to the DNS server.
        /// </summary>
        /// <param name="question">The question to add to the request</param>
        public void AddQuestion(Question question)
        {
            // abandon if null
            if (question == null) throw new ArgumentNullException("question");

            // add this question to our collection
            _questions.Add(question);
        }

        /// <summary>
        /// Convert this request into a byte array ready to send direct to the DNS server
        /// </summary>
        /// <returns></returns>
        public byte[] GetMessage()
        {
            // construct a message for this request. This will be a byte array but we're using
            // an arraylist as we don't know how big it will be
            ArrayList data = new ArrayList();

            // the id of this message - this will be filled in by the resolver
            data.Add((byte)0);
            data.Add((byte)0);

            // write the bitfields
            data.Add((byte)(((byte)_opCode << 3) | (_recursionDesired ? 0x01 : 0)));
            data.Add((byte)0);

            // tell it how many questions
            unchecked
            {
                data.Add((byte)(_questions.Count >> 8));
                data.Add((byte)_questions.Count);
            }

            // the are no requests, name servers or additional records in a request
            data.Add((byte)0); data.Add((byte)0);
            data.Add((byte)0); data.Add((byte)0);
            data.Add((byte)0); data.Add((byte)0);

            // that's the header done - now add the questions
            foreach (Question question in _questions)
            {
                AddDomain(data, question.Domain);
                unchecked
                {
                    data.Add((byte)0);
                    data.Add((byte)question.Type);
                    data.Add((byte)0);
                    data.Add((byte)question.Class);
                }
            }

            // and convert that to an array
            byte[] message = new byte[data.Count];
            data.CopyTo(message);
            return message;
        }

        /// <summary>
        /// Adds a domain name to the ArrayList of bytes. This implementation does not use
        /// the domain name compression used in the class Pointer - maybe it should.
        /// </summary>
        /// <param name="data">The ArrayList representing the byte array message</param>
        /// <param name="domainName">the domain name to encode and add to the array</param>
        private static void AddDomain(ArrayList data, string domainName)
        {
            int position = 0;
            int length = 0;

            // start from the beginning and go to the end
            while (position < domainName.Length)
            {
                // look for a period, after where we are
                length = domainName.IndexOf('.', position) - position;

                // if there isn't one then this labels length is to the end of the string
                if (length < 0) length = domainName.Length - position;

                // add the length
                data.Add((byte)length);

                // copy a char at a time to the array
                while (length-- > 0)
                {
                    data.Add((byte)domainName[position++]);
                }

                // step over '.'
                position++;
            }

            // end of domain names
            data.Add((byte)0);
        }
    }
}
